package com.scheduling.schedule.service;

import com.scheduling.machine.entity.Machine;
import com.scheduling.machine.mapper.MachineMapper;
import com.scheduling.schedule.entity.MachineLoad;
import com.scheduling.schedule.entity.ProductionTask;
import com.scheduling.schedule.entity.Tuple2;
import com.scheduling.schedule.mapper.MachineLoadMapper;
import com.scheduling.schedule.mapper.ProductionTaskMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 机器负荷服务类
 * 处理机器负荷表的查询、更新等操作
 *
 * @author 开发团队
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MachineLoadService {

    private final MachineLoadMapper machineLoadMapper;
    private final MachineMapper machineMapper;
    private final ProductionTaskMapper productionTaskMapper;

    /**
     * 获取机器的空闲时间集合
     *
     * @param scheduleStartTime 排产开始时间
     * @return 空闲时间集合
     */
    public MultiValueMap<String, Tuple2<LocalDateTime, LocalDateTime>> getCurrentMachineAvailablePeriod(LocalDateTime scheduleStartTime) {
        log.debug("获取所有机器当前可用时间段");

        MultiValueMap<String, Tuple2<LocalDateTime, LocalDateTime>> machineAvailability = new LinkedMultiValueMap<>();
        MultiValueMap<String, Tuple2<LocalDateTime, LocalDateTime>> machineLoads = new LinkedMultiValueMap<>();

        try {
            // 查询所有机器的负荷情况
            List<MachineLoad> allLoads = machineLoadMapper.selectAll();

            for (MachineLoad load : allLoads) {
                //若该任务负载结束时间在 排产开始之前，则其在排产中无用
                if (load.getEndTime().isBefore(scheduleStartTime)) {
                    continue;
                }
                String machineId = load.getMachineId();
                Tuple2<LocalDateTime, LocalDateTime> duringTime = new Tuple2<>(load.getStartTime(), load.getEndTime());

                machineLoads.add(machineId, duringTime);
            }

            //计算空闲时间
            for (Map.Entry<String, List<Tuple2<LocalDateTime, LocalDateTime>>> entry : machineLoads.entrySet()) {
                String machineId = entry.getKey();
                List<Tuple2<LocalDateTime, LocalDateTime>> duringTimes = new ArrayList<>(entry.getValue());

                // 按开始时间升序排序
                duringTimes.sort(Comparator.comparing(Tuple2::getFirst));

                List<Tuple2<LocalDateTime, LocalDateTime>> freeTimes = new ArrayList<>();
                LocalDateTime currentEnd = scheduleStartTime;

                // 遍历每个占用时间段
                for (Tuple2<LocalDateTime, LocalDateTime> during : duringTimes) {
                    LocalDateTime busyStart = during.getFirst();
                    LocalDateTime busyEnd = during.getSecond();

                    // 跳过完全在 scheduleStartTime 之前的时间段
                    if (busyEnd.isBefore(scheduleStartTime) || busyEnd.equals(scheduleStartTime)) {
                        continue;
                    }

                    // 当前占用时间段有部分在 scheduleStartTime 之后
                    if (busyStart.isAfter(currentEnd)) {
                        // 找到空闲段：currentEnd 到 busyStart
                        freeTimes.add(new Tuple2<>(currentEnd, busyStart));
                        currentEnd = busyEnd;
                    } else {
                        // 更新当前已覆盖的最晚时间（处理重叠）
                        if (busyEnd.isAfter(currentEnd)) {
                            currentEnd = busyEnd;
                        }
                    }
                }

                // 添加最后一个空闲段（从最后一个占用结束到无穷远）
                freeTimes.add(new Tuple2<>(currentEnd, LocalDateTime.MAX));
                machineAvailability.put(machineId, freeTimes);
            }

            //对于未有负荷的机器, 初始化为 MachineID (scheduleStartTime, LocalDateTime.MAX)
            List<Machine> allMachines = machineMapper.selectAllMachines();
            Set<String> machineIds = machineAvailability.keySet();
            for (Machine machine : allMachines) {
                if (machineIds.contains(machine.getMachineId())) {
                    continue;
                }
                List<Tuple2<LocalDateTime, LocalDateTime>> freeTimes = new ArrayList<>();
                freeTimes.add(new Tuple2<>(scheduleStartTime, LocalDateTime.MAX));
                machineAvailability.put(machine.getMachineId(), freeTimes);
            }

            log.info("获取到 {} 台机器的负荷信息", machineAvailability.size());
        } catch (Exception e) {
            log.error("获取机器可用时间失败", e);
            throw new RuntimeException("获取机器负荷信息失败: " + e.getMessage());
        }
        return machineAvailability;
    }

    /**
     * 获取所有机器的当前可用时间
     * 基于machine_load表中的数据计算每台机器最早可用时间
     *
     * @return 机器ID -> 最早可用时间的映射
     */
    public Map<String, LocalDateTime> getCurrentMachineAvailability() {
        log.debug("获取所有机器的当前可用时间");

        Map<String, LocalDateTime> machineAvailability = new HashMap<>();

        try {
            // 查询所有机器的负荷情况
            List<MachineLoad> allLoads = machineLoadMapper.selectAll();

            // 按机器分组，找出每台机器的最晚结束时间
            Map<String, LocalDateTime> latestEndTime = new HashMap<>();

            for (MachineLoad load : allLoads) {
                String machineId = load.getMachineId();
                LocalDateTime endTime = load.getEndTime();

                if (endTime != null) {
                    LocalDateTime currentLatest = latestEndTime.get(machineId);
                    if (currentLatest == null || endTime.isAfter(currentLatest)) {
                        latestEndTime.put(machineId, endTime);
                    }
                }
            }

            // 设置机器可用时间（当前时间和最晚结束时间的较大值）
            LocalDateTime now = LocalDateTime.now();
            for (Map.Entry<String, LocalDateTime> entry : latestEndTime.entrySet()) {
                String machineId = entry.getKey();
                LocalDateTime endTime = entry.getValue();

                LocalDateTime availableTime = endTime.isAfter(now) ? endTime : now;
                machineAvailability.put(machineId, availableTime);

                log.debug("机器 {} 最早可用时间: {}", machineId, availableTime);
            }

            log.info("获取到 {} 台机器的负荷信息", machineAvailability.size());

        } catch (Exception e) {
            log.error("获取机器可用时间失败", e);
            throw new RuntimeException("获取机器负荷信息失败: " + e.getMessage());
        }

        return machineAvailability;
    }

    /**
     * 获取指定机器的当前可用时间
     *
     * @param machineId 机器ID
     * @return 机器最早可用时间
     */
    public LocalDateTime getMachineAvailability(String machineId) {
        log.debug("获取机器 {} 的可用时间", machineId);

        try {
            MachineLoad latestLoad = machineLoadMapper.getLatestLoadByMachineId(machineId);

            LocalDateTime now = LocalDateTime.now();
            if (latestLoad == null || latestLoad.getEndTime() == null) {
                log.debug("机器 {} 当前无负荷，立即可用", machineId);
                return now;
            }

            LocalDateTime endTime = latestLoad.getEndTime();
            LocalDateTime availableTime = endTime.isAfter(now) ? endTime : now;

            log.debug("机器 {} 最早可用时间: {}", machineId, availableTime);
            return availableTime;

        } catch (Exception e) {
            log.error("获取机器 {} 可用时间失败", machineId, e);
            return LocalDateTime.now(); // 失败时返回当前时间
        }
    }

    /**
     * 添加新的机器负荷记录
     * 当生产任务分配到机器时调用
     *
     * @param machineId      机器ID
     * @param taskId         任务ID
     * @param startTime      开始时间
     * @param endTime        结束时间
     * @param loadPercentage 负荷百分比
     */
    public void addMachineLoad(String machineId, String taskId,
                               LocalDateTime startTime, LocalDateTime endTime,
                               Integer loadPercentage) {
        log.debug("添加机器负荷: 机器={}, 任务={}, 时间={} - {}",
                machineId, taskId, startTime, endTime);

        try {
            MachineLoad machineLoad = new MachineLoad();
            machineLoad.setMachineId(machineId);
            machineLoad.setTaskId(taskId);
            machineLoad.setStartTime(startTime);
            machineLoad.setEndTime(endTime);
            machineLoad.setLoadPercentage(loadPercentage != null ? loadPercentage : 100);
            machineLoad.setCreateTime(LocalDateTime.now());

            int result = machineLoadMapper.insert(machineLoad);
            if (result > 0) {
                log.debug("机器负荷记录添加成功: {}", machineLoad.getLoadId());
            } else {
                log.warn("机器负荷记录添加失败");
            }

        } catch (Exception e) {
            log.error("添加机器负荷失败", e);
            throw new RuntimeException("添加机器负荷失败: " + e.getMessage());
        }
    }

    /**
     * 批量添加机器负荷记录
     *
     * @param machineLoads 机器负荷列表
     */
    public void batchAddMachineLoads(List<MachineLoad> machineLoads) {
        log.debug("批量添加机器负荷记录，数量: {}", machineLoads.size());

        try {
            for (MachineLoad load : machineLoads) {
                if (load.getCreateTime() == null) {
                    load.setCreateTime(LocalDateTime.now());
                }
                if (load.getLoadPercentage() == null) {
                    load.setLoadPercentage(100);
                }
            }

            int result = machineLoadMapper.batchInsert(machineLoads);
            log.info("批量添加机器负荷记录完成，成功数量: {}", result);

        } catch (Exception e) {
            log.error("批量添加机器负荷失败", e);
            throw new RuntimeException("批量添加机器负荷失败: " + e.getMessage());
        }
    }

    /**
     * 删除指定任务的机器负荷记录
     * 当任务被取消或重新排产时调用
     *
     * @param taskId 任务ID
     */
    public void removeMachineLoadByTask(String taskId) {
        log.debug("删除任务 {} 的机器负荷记录", taskId);

        try {
            int result = machineLoadMapper.deleteByTaskId(taskId);
            log.debug("删除任务 {} 的机器负荷记录完成，影响行数: {}", taskId, result);

        } catch (Exception e) {
            log.error("删除任务 {} 的机器负荷记录失败", taskId, e);
            throw new RuntimeException("删除机器负荷记录失败: " + e.getMessage());
        }
    }

    /**
     * 获取机器在指定时间段的负荷情况
     * 用于排产冲突检测
     *
     * @param machineId 机器ID
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @return 该时间段内的负荷记录列表
     */
    public List<MachineLoad> getMachineLoadInPeriod(String machineId,
                                                    LocalDateTime startTime,
                                                    LocalDateTime endTime) {
        log.debug("查询机器 {} 在时间段 {} - {} 的负荷情况", machineId, startTime, endTime);

        try {
            List<MachineLoad> loads = machineLoadMapper.selectByMachineAndPeriod(machineId, startTime, endTime);
            log.debug("机器 {} 在指定时间段有 {} 条负荷记录", machineId, loads.size());
            return loads;

        } catch (Exception e) {
            log.error("查询机器负荷失败", e);
            throw new RuntimeException("查询机器负荷失败: " + e.getMessage());
        }
    }

    /**
     * 检查机器在指定时间段是否有时间冲突
     *
     * @param machineId 机器ID
     * @param startTime 计划开始时间
     * @param endTime   计划结束时间
     * @return true表示有冲突，false表示无冲突
     */
    public boolean hasTimeConflict(String machineId, LocalDateTime startTime, LocalDateTime endTime) {
        List<MachineLoad> conflicts = getMachineLoadInPeriod(machineId, startTime, endTime);
        boolean hasConflict = !conflicts.isEmpty();

        if (hasConflict) {
            log.warn("机器 {} 在时间段 {} - {} 存在 {} 个时间冲突",
                    machineId, startTime, endTime, conflicts.size());
        }

        return hasConflict;
    }

    /**
     * 获取机器负荷统计信息
     * 用于排产结果分析
     *
     * @param machineIds 机器ID列表
     * @param startTime  统计开始时间
     * @param endTime    统计结束时间
     * @return 机器负荷统计
     */
    public Map<String, Double> getMachineUtilizationStats(List<String> machineIds,
                                                          LocalDateTime startTime,
                                                          LocalDateTime endTime) {
        log.debug("计算机器利用率统计，机器数量: {}, 时间范围: {} - {}",
                machineIds.size(), startTime, endTime);

        Map<String, Double> utilizationStats = new HashMap<>();
        long totalMinutes = java.time.Duration.between(startTime, endTime).toMinutes();

        for (String machineId : machineIds) {
            try {
                List<MachineLoad> loads = getMachineLoadInPeriod(machineId, startTime, endTime);

                long workMinutes = loads.stream()
                        .mapToLong(load -> {
                            LocalDateTime loadStart = load.getStartTime().isBefore(startTime) ? startTime :
                                    load.getStartTime();
                            LocalDateTime loadEnd = load.getEndTime().isAfter(endTime) ? endTime : load.getEndTime();
                            return java.time.Duration.between(loadStart, loadEnd).toMinutes();
                        })
                        .sum();

                double utilization = totalMinutes > 0 ? (double) workMinutes / totalMinutes * 100 : 0.0;
                utilizationStats.put(machineId, utilization);

                log.debug("机器 {} 利用率: {:.2f}%", machineId, utilization);

            } catch (Exception e) {
                log.error("计算机器 {} 利用率失败", machineId, e);
                utilizationStats.put(machineId, 0.0);
            }
        }

        return utilizationStats;
    }

    public void batchAddProductionTask(List<ProductionTask> productionTasks) {
        log.debug("批量添加生产任务, size:{}", productionTasks.size());
        try {
            int result = productionTaskMapper.batchInsert(productionTasks);
            log.info("批量插入生产任务完成， 任务数目: {}", result);
        } catch (Exception e) {
            log.error("批量添加生产任务失败", e);
            throw new RuntimeException("批量添加生产任务失败: " + e.getMessage());
        }
    }
}